package com.wang.friend.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wang.common.message.AliSmsService;
import com.wang.common.security.exception.ServiceException;
import com.wang.common.security.service.TokenService;
import com.wang.friend.domain.user.User;
import com.wang.friend.domain.user.dto.UserDTO;
import com.wang.friend.domain.user.dto.UserUpdateDTO;
import com.wang.friend.domain.user.vo.LoginUserVO;
import com.wang.friend.domain.user.vo.UserVO;
import com.wang.friend.manager.UserCacheManager;
import com.wang.friend.mapper.user.UserMapper;
import com.wang.friend.service.IUserService;
import com.wang.common.core.constants.CacheConstants;
import com.wang.common.core.constants.Constant;
import com.wang.common.core.constants.HttpConstants;
import com.wang.common.core.domain.LoginUser;
import com.wang.common.core.domain.R;
import com.wang.common.core.enums.ResultCode;
import com.wang.common.core.enums.UserIdentity;
import com.wang.common.core.enums.UserStatus;
import com.wang.common.core.utils.ThreadLocalUtil;
import com.wang.common.redis.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class UserServiceImpl implements IUserService {

    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration; //验证码过期时间

    @Value("${sms.send-limit:50}")
    private Integer sendLimit; //一天最多发送验证码的次数

    @Value("${jwt.secret}")
    private String secret;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;


    @Autowired
    private RedisService redisService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserCacheManager userCacheManager;

    @Override
    public boolean sendCode(UserDTO userDTO) {

        //发验证码之前要进行必要的校验

        //1.判断手机号的格式是否正确
        if (!checkPhone(userDTO.getPhone())){
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }

        //避免用户频繁操作,一分钟之内只可以操作一次
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());

        Long expire = redisService.getExpire(phoneCodeKey,TimeUnit.SECONDS);

        //phoneCodeExpiration: 验证码过期时间(5分钟)
        //expire: 当前还剩的过期时间
        if (expire != null && (phoneCodeExpiration*60L-expire) < 60L){
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }

        //一天最多发送50次.每日清空记录(在向Redis中插入的时候设置了过期时间)
        String codeTimeKey = getCodeTimeKey(userDTO.getPhone());

        //获取验证码发送次数
        Long sendTimes = redisService.getCacheObject(codeTimeKey, Long.class);
        if (sendTimes != null && sendTimes > sendLimit){
            //如果发送次数 > 50次, 返回错误信息"当天请求次数已达到上限"
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }

        String verifyCode = "123456"; //验证码默认生成123456

        //把验证码存储到Redis中
        redisService.setCacheObject(phoneCodeKey,verifyCode,phoneCodeExpiration, TimeUnit.MINUTES);

        //验证码发送次数+1.
        redisService.increment(codeTimeKey);

        //如果 sendTime == null 说明是第一次发验证码 --> 为计数器设置过期时间 (每日0点清空)
        if (sendTimes == null){
            //计算当前时间距离明天凌晨0点的秒数, 然后将这个秒数作为过期时间设置给Redis中的codeTimeKey
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey,seconds,TimeUnit.SECONDS);
        }

        return true;
    }

    @Override
    public String codeLogin(UserDTO userDTO) {

        //1.首先校验验证码 (不管是新用户还是老用户)
        checkCode(userDTO.getPhone(),userDTO.getCode());

        //2.验证码校验正确之后 再判断 是新用户还是老用户
        //从数据库中查有没有和当前用户手机号相同的用户
        //SELECT * FROM user WHERE phone = #{phone};
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone,userDTO.getPhone()));

        //如果为null,说明是新用户
        if (user == null){
            user = new User();
            user.setPhone(userDTO.getPhone()); //设置手机号
            user.setNickName(Constant.USER_NICK_PREFIX + RandomUtil.randomNumbers(10)); //为用户设置默认昵称
            user.setCreateBy(Constant.SYSTEM_USER_ID); //创建人设置为系统创建
            user.setCreateTime(LocalDateTime.now()); //创建时间设置为当前时间
            user.setStatus(UserStatus.NORMAL.getStatus()); //设置用户状态为正常
            userMapper.insert(user);
        }


        //验证成功之后,返回token
        return tokenService.createToken(user.getUserId(), UserIdentity.ORDINARY.getValue(), user.getNickName(),user.getHeadImage(),secret);
    }

    @Override
    public boolean logout(String token) {
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) { //检查token是否有效
            //去除token中的前缀和空值
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }

        //从Redis中删除当前用户信息
        return tokenService.deleteLoginUser(token, secret);
    }

    @Override
    public R<LoginUserVO> info(String token) {

        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) { //检查token是否有效
            //去除token中的前缀和空值
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }

        //从Redis中获取当前用户的信息
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if (loginUser == null) {
            return R.fail();
        }

        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        if (StrUtil.isNotEmpty(loginUser.getHeadImage())){
            loginUser.setHeadImage(downloadUrl + loginUser.getHeadImage());
        }

        return R.success(loginUserVO);
    }

    @Override
    public UserVO detail() {

        //1.从本地线程中获取到当前用户的id
        Long userId = ThreadLocalUtil.get(Constant.USER_ID,Long.class);

        //获取失败
        if (userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }

        //2.从缓存中获取用户信息 (如果不存在,则从数据库中获取到,再刷新缓存)
        UserVO userVO = userCacheManager.getUserById(userId);

        //如果为空,抛出异常
        if (userVO == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }

        //设置用户头像在oss中的路径
        if (StrUtil.isNotEmpty(userVO.getHeadImage())){
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }

        return userVO;
    }

    @Override
    public int edit(UserUpdateDTO userUpdateDTO) {

        //1.从本地线程中获取userId
        Long userId = ThreadLocalUtil.get(Constant.USER_ID,Long.class);

        if (userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }

        //2.从数据库中查询用户数据
        User user = userMapper.selectById(userId);
        if (user == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }

        //3.设置更新之后的用户信息
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());

        //4.刷新缓存数据
        userCacheManager.refreshUser(user);

        //5.更新首页中用户的昵称和头像 (存在缓存中的信息)
        tokenService.refreshLoginUser(user.getNickName(), user.getHeadImage(),
                ThreadLocalUtil.get(Constant.USER_ID, Long.class));

        //6.更新数据库的信息
        //UPDATE user SET ... ... WHERE user_id = #{userId};
        return userMapper.updateById(user);
    }

    @Override
    public int updateHeadImage(String headImage) {

        //1.从本地线程中获取userId
        Long userId = ThreadLocalUtil.get(Constant.USER_ID,Long.class);

        if (userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }

        //2.从数据库中查询用户数据
        User user = userMapper.selectById(userId);
        if (user == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }

        //3.设置更新之后的用户头像信息
        user.setHeadImage(headImage);

        //4.刷新缓存
        userCacheManager.refreshUser(user);

        //5.更新首页中用户的昵称和头像 (存在缓存中的信息)
        tokenService.refreshLoginUser(user.getNickName(), user.getHeadImage(),
                ThreadLocalUtil.get(Constant.USER_ID, Long.class));

        //6.更新数据库的信息
        return userMapper.updateById(user);
    }


    /**
     * 检查手机号是否正确
     * @param phone 手机号
     * @return 返回手机号是否正确
     */
    public static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }

    /**
     * 校验验证码
     * @param phone 手机号
     * @param code 验证码
     */
    private void checkCode(String phone,String code){
        //1.从缓存中查出验证码
        //获取key
        String phoneCodeKey = getPhoneCodeKey(phone);
        //根据key从redis中获取验证码
        String verifyCode = redisService.getCacheObject(phoneCodeKey, String.class);

        //2.校验验证码
        //第一种情况: 还没有发送验证码 (获取到的验证码为空)
        if (StrUtil.isEmpty(verifyCode)){
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        }
        //第二种情况: 验证码错误
        if (!verifyCode.equals(code)){
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }

        //3.校验通过,删除验证码
        //验证码校验成功,从缓存中删除
        redisService.deleteObject(phoneCodeKey);
    }
    /**
     * 生成Redis存储验证码的key
     * @param phone 需要添加验证码的手机
     * @return 返回RedisKey
     */
    private String getPhoneCodeKey(String phone){
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    /**
     * 生成Redis存储发送验证码次数的key
     * @param phoneNumber 手机号码
     * @return 返回Redis中存储的key
     */
    private String getCodeTimeKey(String phoneNumber){
        return CacheConstants.SEND_CODE_TIMES_KEY + phoneNumber;
    }
}